#! /usr/bin/env python
#
#  almacencito.py v0.1
#
#  Copyright 2009 Alejandro Moreno <alexmoreno@gmail.com>
#
#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.
#
#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  GNU General Public License for more details.
#
#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
#  MA 02110-1301, USA.
#

import boto
import os
import pdb
import sqlite3
import sys
from binascii import crc32
from datetime import datetime
from multiprocessing import Process
from pprint import pprint
from threading import Timer, Lock

VERSION = '0.1'
BUCKET_NAME = 'alejandro.moreno'
DEFAULT_DEPTH = 1

path_list = (
    ('/home/share/photos', 9),
)
db_path = os.path.join(os.getcwd(), '.almacencito.db')
secs_between_threads = 3.0

def walk_with_depth(base, depth=1):
    for root, dirs, files in os.walk(base):
        if len(root[len(base):].split(os.sep)) <= depth:
            yield root, dirs, files

def check_input():
    print('Checking input ...')
    for item in path_list:
        if type(item) == type(()): path = item[0]
        else: path = item
        if not os.path.exists(path):
            print("\nPath '{0}' was not found.\n".format(path))
            sys.exit(1)
    print('    path_list was OK.')

    conn = sqlite3.connect(db_path)
    if type(conn) != sqlite3.Connection:
        print("\nConnection could not be made to '{0}'\n".format(db_path))
        sys.exit(1)
    print('    db_path was OK.')

    conn.execute('''
            CREATE TABLE IF NOT EXISTS filelist
                (
                    fullname    text PRIMARY KEY,
                    size        integer,
                    date        text,
                    crc         text,
                    uploaded    integer,
                    ins_date    text
                )
            ''')
    conn.commit()

def thread_starter(p_sched, p_upl, db_path, lock):
    print('thread_starter() ...')

    if (lock):
        lock.acquire()

    conn = sqlite3.connect(db_path)
    conn.row_factory = sqlite3.Row
    count = 0
    with conn:
        count = conn.execute('''
                SELECT count(*)
                FROM filelist
                WHERE uploaded = 0
                ''').fetchone()[0]

    if (lock):
        lock.release()

    if count > 0:
        print('    {0} updates were found pending. Running '
            'uploader thread ...'.format(count))
        if not p_upl.started:
            p_upl.start()
            p_upl.join(1)
            p_upl.started = 1
    else:
        print('    No uploads pending. Checking scheduler thread status ...')
        if not p_sched.started:
            print('    Starting scheduler thread ...')
            p_sched.start()
            p_sched.join(1)
            p_sched.started = 1
        else:
            print('    Scheduler has already run.')


def scheduler(db_path, lock):
    print("*** I'm scheduling! ***")
    for item in path_list:
        try: path, depth = item
        except ValueError:
            if type(item) == type(()): path = item[0]
            else: path = item   # guessing it's just a string
            depth = DEFAULT_DEPTH
        for root, dirs, files in walk_with_depth(path, depth):
            for fullname in [os.path.join(root, fname) for fname in files \
                                if fname[0:1] != '.']:
                fstat = os.stat(fullname)

                f = open(fullname, 'rb')
                crc = crc32(f.read()) & 0xffffffff
                crc = '{0:x}'.format(crc)

                with lock:
                    cmd = ''
                    params = [fstat.st_size,
                                datetime.fromtimestamp(fstat.st_mtime),
                                crc,
                                0,
                                datetime.today()]
                    conn = sqlite3.connect(db_path)
                    conn.row_factory = sqlite3.Row
                    row = conn.execute('''
                            select fullname, size, date, crc
                              from filelist
                             where fullname = ?''', (fullname,)).fetchone()
                    if row is None:
                        cmd = '''insert into filelist
                                values (?,?,?,?,?,?)'''
                        params.insert(0, fullname)
                        print('insert ', params)
                    elif row['crc'] != crc \
                    and row['date'] < datetime.fromtimestamp(fstat.st_mtime):
                        cmd = '''update filelist set
                                    size = ?,
                                    date = ?,
                                    crc = ?,
                                    uploaded = ?,
                                    ins_date = ?
                                where fullname = ?'''
                        params.append(fullname)
                        print('update ', params)
                    else:
                        continue

                    conn.execute(cmd, tuple(params))
                    conn.commit()


def uploader(db_path, lock):
    print("*** I'm uploading! ***")
    while True:
        conn = sqlite3.connect(db_path)
        conn.row_factory = sqlite3.Row
        row = conn.execute('''
                select * from filelist
                where uploaded = 0
                order by ins_date asc
                limit 1
                ''').fetchone()
        conn.close()
        if row is None:
            break

        # upload takes a while
        s3 = boto.connect_s3()
        bucket = s3.create_bucket(BUCKET_NAME)

        key = boto.s3.Key(bucket)
        key.key = row['fullname']
        key.set_metadata('size', str(row['size']))
        key.set_metadata('date', row['date'])
        key.set_metadata('crc', row['crc'])
        key.set_contents_from_filename(row['fullname'])
        key.close()

        update_proc = ''
        params = ()

        # check uploaded file
        key = bucket.lookup(row['fullname'])
        if key and key.get_metadata('crc') == row['crc']:
            update_proc = '''
                    update filelist
                    set uploaded = 1
                    where fullname = ?'''
            params = (row['fullname'],)
        else:
            update_proc = '''
                    update filelist
                    set uploaded = 0,
                        ins_date = ?
                    where fullname = ?'''
            params = (datetime.today(), row['fullname'])

        # all is good
        with lock:
            print('upload ', row['fullname'])
            conn = sqlite3.connect(db_path)
            conn.execute(update_proc, params)
            conn.commit()


if __name__ == '__main__':
    check_input()
    lock = Lock()

    #conn = sqlite3.connect(db_path)
    #conn.execute('delete from filelist')
    #conn.commit()

    p_sched = Process(target=scheduler, args=(db_path, lock))
    p_sched.started = 0
    p_upl = Process(target=uploader, args=(db_path, lock))
    p_upl.started = 0

    thread_starter(p_sched, p_upl, db_path, lock)
    print('Done first run of thread_starter()')
    t = Timer(secs_between_threads, thread_starter,
            args=(p_sched, p_upl, db_path, lock))
    t.start()
    print('End of parent thread.')
